﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Script.Serialization;
using System.Web.SessionState;

namespace ClassLibrary1
{
    public abstract class RestHttpHandler : IHttpHandler, IReadOnlySessionState
    {
        public const string ParamKeyId = "id";
        public const string ParamKeyQuery = "query";

        /// <summary>
        ///  RouteData property gives an access to request data provided by the router
        ///  It has a setter to simplify instantiation from the RestRouteHandler class
        ///  </summary>
        public RouteData RouteData { get; set; }

        protected bool HasId
        {
            get { return this.RouteData.Values[ParamKeyId] != null; }
        }
        protected bool HasQuery
        {
            get { return this.RouteData.Values[ParamKeyQuery] != null; }
        }
        protected int ParseId()
        {
            return int.Parse(this.RouteData.Values[ParamKeyId].ToString());
        }
        protected NameValueCollection ParseQuery()
        {
            var regex = new Regex("(?<key>[a-zA-Z\\-]+)($|/)(?<value>[^/]+)?");
            var matches = regex.Matches(this.RouteData.Values[ParamKeyQuery].ToString());
            var result = new NameValueCollection();
            foreach (Match match in matches)
            {
                result.Add(match.Groups["key"].Value, match.Groups["value"].Value);
            }
            return result;
        }
        public bool IsReusable
        {
            get { return false; }
        }
        public abstract void ProcessRequest(HttpContext context);
    }

    public abstract class RestHttpHandler<T, TBinder> : RestHttpHandler
        where T : class
        where TBinder : DefaultModelBinder, new()
    {
        /// <summary>
        ///  ProcessRequest actually does request mapping to one of CRUD actions
        ///  </summary>
        public override void ProcessRequest(HttpContext context)
        {
            var @params = new NameValueCollection { context.Request.Form, context.Request.QueryString };
            foreach (var value in this.RouteData.Values)
            {
                @params.Add(value.Key, value.Value.ToString());
            }
            RenderHeader(context);
            if (context.Request.HttpMethod == "GET")
            {
                if (this.HasQuery)
                {
                    @params.Add(this.ParseQuery());
                    this.Render(context, this.GetBy(@params));
                }
                else
                {
                    if (this.HasId)
                    {
                        this.Render(context, this.GetBy(this.ParseId()));
                    }
                    else
                    {
                        this.Render(context, this.GetAll());
                    }
                }
            }
            else
            {
                var entity = BindModel(@params);
                switch (context.Request.HttpMethod)
                {
                    case "POST":
                        this.Create(entity);
                        break;
                    case "PUT":
                        this.Update(entity);
                        break;
                    case "DELETE":
                        this.Delete(entity);
                        break;
                    default:
                        throw new NotSupportedException();
                }
                this.Render(context, entity);
            }
        }
        protected abstract T GetBy(int id);
        protected abstract IEnumerable<T> GetBy(NameValueCollection query);
        protected abstract IEnumerable<T> GetAll();
        protected abstract void Create(T entity);
        protected abstract void Update(T entity);
        protected abstract void Delete(T entity);
        protected abstract object ToJson(T entity);
        private object ToJson(IEnumerable<T> entities)
        {
            return (
                from entity in entities
                select this.ToJson(entity)).ToArray();
        }
        private static T BindModel(NameValueCollection @params)
        {
            return new TBinder().BindModel(
                new ControllerContext(),
                new ModelBindingContext
                    {
                        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(T)),
                        ValueProvider = new NameValueCollectionValueProvider(
                            @params,
                            CultureInfo.InvariantCulture
                            )
                    }
                ) as T;
        }
        private static void RenderHeader(HttpContext context)
        {
            context.Response.ClearHeaders();
            context.Response.ClearContent();
            context.Response.ContentType = "application/json";
            context.Response.ContentEncoding = Encoding.UTF8;
            var cachePolicy = context.Response.Cache;
            cachePolicy.SetCacheability(HttpCacheability.Public);
            cachePolicy.SetMaxAge(TimeSpan.FromSeconds(30.0));
        }
        private void Render(HttpContext context, IEnumerable<T> entities)
        {
            Render(context, RuntimeHelpers.GetObjectValue(this.ToJson(entities)));
        }
        private void Render(HttpContext context, T entity)
        {
            Render(context, RuntimeHelpers.GetObjectValue(this.ToJson(entity)));
        }
        private static void Render(HttpContext context, object result)
        {
            context.Response.Write(
                new JavaScriptSerializer().Serialize(
                    RuntimeHelpers.GetObjectValue(result)
                    )
                );
        }
    }
}
